namespace egret.socketio {

    let events = {
        connect: 1,
        connect_error: 1,
        connect_timeout: 1,
        connecting: 1,
        disconnect: 1,
        error: 1,
        reconnect: 1,
        reconnect_attempt: 1,
        reconnect_failed: 1,
        reconnect_error: 1,
        reconnecting: 1,
        ping: 1,
        pong: 1
    };

    export class Socket extends Emitter {

        id: string;

        private connected: boolean;
        private disconnected: boolean;

        private ids: number;
        private nsp: string;
        private io: Manager;

        private uri: Uri;

        private flags;
        private subs: Handle[];
        private receiveBuffer = [];
        private sendBuffer = [];
        private acks: { [idx: number]: Function } = {};

        constructor(io: Manager, nsp: string, opts?: Uri) {
            super();
            this.io = io;
            this.nsp = nsp;
            this.ids = 0;
            this.connected = false;
            this.disconnected = true;

            this.flags = {};
            this.uri = opts;

            if (this.io.autoConnect) this.open();
        }

        private subEvents(): void {
            if (this.subs) return;

            let io = this.io;

            this.subs = [
                on(io, _emEvent.EVENT_OPEN, this.onopen, this),
                on(io, _emEvent.EVENT_PACKET, this.onpacket, this),
                on(io, _emEvent.EVENT_CLOSE, this.onclose, this)
            ]
        }

        public open(): Socket {
            new Promise((resolve) => {
                if (this.connected) return;

                this.subEvents();
                this.io.open();

                if (this.io.readyState === _emReadyState.OPEN)
                    this.onopen();

                this.emit(_emEvent.EVENT_CONNECTING);
            });

            return this;
        }

        public send(...args): Socket {
            args.unshift(_emEvent.EVENT_MESSAGE);
            this.emit.apply(this, args);
            return this;
        }

        public emit(event: string, ...args): Socket {
            if (events.hasOwnProperty(event)) {
                super.emit(event, args);
                return this;
            }

            args.unshift(event);
            let data = toArray(args);
            let packet: Packet = {
                type: /*(this.flags.binary !== undefined ? this.flags.binary : hasBin(args)) ? parser.BINARY_EVENT :*/ _emParser.EVENT,
                data: data
            };
            packet.options = {};
            packet.options.compress = !this.flags || false !== this.flags.compress;

            if (typeof data[data.length - 1] === 'function') {
                console.debug('emitting packet with ack id ' + this.ids);
                this.acks[this.ids] = data.pop();
                packet.id = this.ids++;
            }

            if (this.connected) {
                this.packet(packet);
            } else {
                this.sendBuffer.push(packet);
            }
            return this;
        }

        public connnect(): Socket {
            return this.open();
        }

        public close(): Socket {
            return this.disconnect();
        }

        private disconnect(): Socket {
            if (this.connected) {
                console.debug('performing disconnect ' + this.nsp);
                this.packet({ type: _emParser.DISCONNECT });
            }

            this.destroy();

            if (this.connected) {
                this.onclose('io client disconnect');
            }
            return this;
        }

        private packet(packet: Packet): void {
            packet.nsp = this.nsp;
            this.io.packet(packet);
        }

        private ack(id: number): Function {
            let sent = false;

            return (...args) => {
                if (sent)
                    return;
                sent = true;

                let _args = toArray(args);

                this.packet(
                    {
                        type: _emParser.ACK,
                        id: id,
                        data: _args
                    }
                );
            }
        }

        private destroy(): void {
            if (this.subs) {
                for (let i = 0; i < this.subs.length; i++) {
                    this.subs[i].destory();
                }
                this.subs = null;
            }
            this.io.destroy(this);
        }

        private emitBuffered(): void {
            //TODO
            for (let k in this.receiveBuffer) {
                super.emit.apply(this, this.receiveBuffer[k]);
            }
            this.receiveBuffer = [];

            for (let k in this.sendBuffer) {
                this.packet(this.sendBuffer[k]);
            }
            this.sendBuffer = [];
        }

        private onopen(): void {
            console.debug('transport is open - connecting');

            if ('/' !== this.nsp) {
                if (this.uri.query()) {
                    this.packet({ type: _emParser.CONNECT, query: this.uri.query() });
                } else {
                    this.packet({ type: _emParser.CONNECT })
                }
            }

        }

        private onpacket(packet: Packet) {
            let sameNamespace = packet.nsp === this.nsp;
            let rootNamespaceError = packet.type === _emParser.ERROR && packet.nsp === '/';

            if (!sameNamespace && !rootNamespaceError) return;

            switch (packet.type) {
                case _emParser.CONNECT:
                    this.onconnect();
                    break;
                case _emParser.EVENT:
                    this.onevent(packet);
                    break;
                case _emParser.BINARY_EVENT:
                    this.onevent(packet);
                    break;
                case _emParser.ACK:
                    this.onack(packet);
                    break;

                case _emParser.BINARY_ACK:
                    this.onack(packet);
                    break;

                case _emParser.DISCONNECT:
                    this.ondisconnect();
                    break;

                case _emParser.ERROR:
                    this.emit(_emEvent.EVENT_ERROR, packet.data);
                    break;
            }
        }

        private onconnect(): void {
            this.connected = true;
            this.emit(_emEvent.EVENT_CONNECT);
            this.emitBuffered();
        }

        private onevent(packet: Packet): void {
            let args = packet.data || [];

            if (packet.id != null) {
                args.push(this.ack(packet.id));
            }

            if (this.connected) {
                super.emit.apply(this, args);
            } else {
                this.receiveBuffer.push(args);
            }
        }

        private onack(packet: Packet): void {
            let ack = this.acks[packet.id];
            if (typeof ack === 'function') {
                ack.apply(this, packet.data);
                delete (this.acks[packet.id]);
            } else {
                console.warn('bad ack ' + packet.id);
            }
        }

        private ondisconnect(): void {
            console.debug('server disconnect ' + this.nsp);
            this.destroy();
            this.onclose('io server disconnect');
        }

        private onclose(reason?: string): void {
            console.debug("close");

            this.connected = false;
            this.disconnected = true;
            delete this.id;
            this.emit(_emEvent.EVENT_DISCONNECT, reason);
        }
    }


}